package monitor

import (
	"fmt"
	"gitee.com/igolang/pkg/boot"
	"gitee.com/igolang/pkg/xutils"
	"github.com/gin-gonic/gin"
	"github.com/shirou/gopsutil/v3/cpu"
	"github.com/shirou/gopsutil/v3/disk"
	"github.com/shirou/gopsutil/v3/host"
	"github.com/shirou/gopsutil/v3/mem"
	"github.com/shirou/gopsutil/v3/process"
	"math"
	"os"
	"runtime"
	"time"
)

var Server = &server{}

type server struct{}

func (s *server) Index(ctx *gin.Context) (map[string]any, error) {
	result := make(map[string]any)
	cpuStats, err := (&server{}).Times()
	if err != nil {
		return nil, err
	}
	result["cpu"] = cpuStats

	memStat, err := s.formatMem()
	if err != nil {
		return nil, err
	}
	result["mem"] = memStat

	gvm, err := s.formatGvm()
	if err != nil {
		return nil, err
	}
	result["gvm"] = gvm

	hostStat, err := s.formatHost()
	if err != nil {
		return nil, err
	}
	result["sys"] = hostStat

	diskStat, err := s.formatDisk()
	if err != nil {
		return nil, err
	}
	result["sysFiles"] = diskStat

	return result, nil
}

func (s *server) formatGvm() (map[string]any, error) {
	var gvm runtime.MemStats
	runtime.ReadMemStats(&gvm)
	p, err := process.NewProcess(int32(boot.Pid))
	if err != nil {
		return nil, err
	}

	processName, err := p.Name()
	if err != nil {
		return nil, err
	}

	processMem, err := p.MemoryInfo()
	if err != nil {
		return nil, err
	}
	totalMem := processMem.RSS
	usedMem := gvm.Alloc
	freeMem := totalMem - usedMem

	return map[string]any{
		"home":      runtime.GOROOT(),
		"inputArgs": fmt.Sprintf("%v", os.Args),
		"name":      processName,
		"runTime":   time.Since(boot.StartTime.Time()).String(),
		"startTime": boot.StartTime,
		"free":      xutils.Round(float64(freeMem)/1024/1024, 2),
		"used":      xutils.Round(float64(usedMem)/1024/1024, 2),
		"total":     xutils.Round(float64(totalMem)/1024/1024, 2),
		"usage":     xutils.Round(float64(usedMem)/float64(totalMem)*100, 2),
		"version":   runtime.Version(),
	}, nil
}

func (s *server) formatDisk() ([]map[string]any, error) {
	ps, err := disk.Partitions(true)
	if err != nil {
		return nil, err
	}
	list := make([]map[string]any, 0)
	for _, v := range ps {
		usage, err := disk.Usage(v.Mountpoint)
		if err != nil {
			return nil, err
		}

		list = append(list, map[string]any{
			"dirName":     usage.Path,
			"sysTypeName": usage.Fstype,
			"typeName":    v.Device,
			"usage":       xutils.Round(usage.UsedPercent, 2),
			"free":        fmt.Sprintf("%.2f GB", float64(usage.Free)/1024/1024/1024),
			"used":        fmt.Sprintf("%.2f GB", float64(usage.Used)/1024/1024/1024),
			"total":       fmt.Sprintf("%.2f GB", float64(usage.Total)/1024/1024/1024),
		})
	}

	return list, nil
}

func (s *server) formatHost() (map[string]any, error) {
	stats, err := host.Info()
	if err != nil {
		return nil, err
	}
	return map[string]any{
		"computerName": stats.Hostname,
		"osArch":       stats.KernelArch,
		"osName":       fmt.Sprintf("%s %s %s %s", stats.Platform, stats.PlatformFamily, stats.PlatformVersion, stats.OS),
		"userDir":      xutils.CurrentAbPath(),
		"computerIp":   xutils.ServerIP().String(),
	}, nil
}

func (s *server) formatMem() (map[string]any, error) {
	stats, err := mem.VirtualMemory()
	if err != nil {
		return nil, err
	}
	// 单位为G
	return map[string]any{
		"total": xutils.Round(float64(stats.Total)/1024/1024/1024, 2),
		"free":  xutils.Round(float64(stats.Available)/1024/1024/1024, 2),
		"used":  xutils.Round(float64(stats.Used)/1024/1024/1024, 2),
		"usage": xutils.Round(stats.UsedPercent, 2),
	}, nil
}

func (s *server) Times() (map[string]any, error) {
	num, err := cpu.Counts(true)
	if err != nil {
		return nil, err
	}
	cpuTimes1, err := cpu.Times(false)
	if err != nil {
		return nil, err
	}
	time.Sleep(time.Second)
	cpuTimes2, err := cpu.Times(false)
	if err != nil {
		return nil, err
	}
	// 计算使用率
	used := s.calculateBusy(cpuTimes1[0], cpuTimes2[0], "user")
	system := s.calculateBusy(cpuTimes1[0], cpuTimes2[0], "sys")
	all := s.calculateBusy(cpuTimes1[0], cpuTimes2[0], "")

	return map[string]any{
		"cpuNum": num,
		"used":   xutils.Round(used, 2),
		"sys":    xutils.Round(system, 2),
		"free":   xutils.Round(100-all, 2),
		"total":  s.Total(cpuTimes2[0]),
		"wait":   cpuTimes2[0].Iowait,
	}, nil
}

func (s *server) Total(t cpu.TimesStat) float64 {
	tot := t.Total()
	if runtime.GOOS == "linux" {
		tot -= t.Guest     // Linux 2.6.24+
		tot -= t.GuestNice // Linux 3.2.0+
	}
	return tot
}

func (s *server) getAllBusy(t cpu.TimesStat, filed string) (float64, float64) {
	tot := t.Total()
	if runtime.GOOS == "linux" {
		tot -= t.Guest     // Linux 2.6.24+
		tot -= t.GuestNice // Linux 3.2.0+
	}

	var busy float64
	if filed == "user" {
		busy = t.User
	} else if filed == "sys" {
		busy = t.System
	} else {
		busy = tot - t.Idle - t.Iowait
	}

	return tot, busy
}

func (s *server) calculateBusy(t1, t2 cpu.TimesStat, field string) float64 {
	t1All, t1Busy := s.getAllBusy(t1, field)
	t2All, t2Busy := s.getAllBusy(t2, field)

	if t2Busy <= t1Busy {
		return 0
	}
	if t2All <= t1All {
		return 100
	}
	return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
}
